package com.brainydroid.example.numerical_distance;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Enumeration;

import org.apache.http.client.ClientProtocolException;


public class CryptoStore {
	
	private static final String MAI_ID_FILENAME = "mai_id";
    private static final String PUBLIC_FILENAME = "key.pub";
    private static final String PRIVATE_FILENAME = "key";
    
    private static final String NOKP_EXCEPTION_MSG = "No keypair present";

    private static final String DEFAULT_CURVE_NAME = "secp112r1";

    private static File maiIdFile;
    private static File publicFile;
    private static File privateFile;
    private static File cacheDir;
    
    private static Crypto crypto;
    private static ServerTalker servertalker;
    private static CryptoStore instance;
    
    private String curveName;

    public static synchronized CryptoStore getInstance(String s, File cd) {
        if (instance == null) {
            instance = new CryptoStore(s, cd);
        }

        return instance;
    }
    
    private CryptoStore(String s, File cd) {
    	crypto = Crypto.getInstance();
    	servertalker = ServerTalker.getInstance(s, this);
    	cacheDir = cd;
    	maiIdFile = new File(cacheDir, MAI_ID_FILENAME);
    	publicFile = new File(cacheDir, PUBLIC_FILENAME);
    	privateFile = new File(cacheDir, PRIVATE_FILENAME);
    	curveName = DEFAULT_CURVE_NAME;
    }
    
    public synchronized void setServerName(String s) {
    	servertalker.setServerName(s);
    }
    
    public synchronized String uploadPublicKey() throws ClientProtocolException, IOException {
    	return servertalker.uploadPublicKey();
    }
    
    public synchronized String signAndUploadData(String ea_id, String data) throws ClientProtocolException, IOException {
    	return servertalker.signAndUploadData(ea_id, data);
    }
    
    private synchronized boolean hasStoredMaiId() {
    	return maiIdFile.exists();
    }
    
    private synchronized boolean hasStoredPrivateKey() {
    	return privateFile.exists();
    }
    
    private synchronized boolean hasStoredPublicKey() {
    	return publicFile.exists();
    }
    
    public synchronized Enumeration<String> getAvailableCurveNames() {
    	return crypto.getAvailableCurveNames();
    }
    
    public synchronized void setCurveName(String s) {
    	curveName = s;
    }
    
    public synchronized boolean hasStoredKeyPairAndMaiId() {
    	if (hasStoredPrivateKey() && hasStoredPublicKey() && hasStoredMaiId()) {
    		return true;
    	} else {
    		clearStore();
    		return false;
    	}
    }
    
    public synchronized byte[] sign(byte[] data) {
    	if (! hasStoredKeyPairAndMaiId()) {
    		throw new RuntimeException(NOKP_EXCEPTION_MSG);
    	}
		try {
			return crypto.sign(getPrivateKey(), data);
		} catch (InvalidKeyException e) {
            throw new RuntimeException(e);
		}
    }
    
    public synchronized boolean generateAndStoreKeyPairAndMaiId()
    		throws ClientProtocolException, IOException {
    	String maiId = servertalker.requestMaiId();
		KeyPair kp = crypto.generateKeyPairNamedCurve(curveName);
    	return storeKeyPairAndMaiId(kp, maiId);
    }
    
    private synchronized boolean storeKeyPairAndMaiId(KeyPair kp, String maiId) {
    	try {
    		clearStore();

        	maiIdFile.createNewFile();
        	BufferedWriter maiIdBuf = new BufferedWriter(new FileWriter(maiIdFile));
            maiIdBuf.write(maiId);
            maiIdBuf.close();
            
        	publicFile.createNewFile();
        	BufferedWriter publicBuf = new BufferedWriter(new FileWriter(publicFile));
            publicBuf.write(Crypto.base64Encode(kp.getPublic().getEncoded()));
            publicBuf.close();

        	privateFile.createNewFile();
        	BufferedWriter privateBuf = new BufferedWriter(new FileWriter(privateFile));
            privateBuf.write(Crypto.base64Encode(kp.getPrivate().getEncoded()));
            privateBuf.close();
            
            return true;
		} catch (IOException e) {
            throw new RuntimeException(e);
		}
    }

    public synchronized KeyPair getKeyPair() {
		return new KeyPair(getPublicKey(), getPrivateKey());
    }
    
    public synchronized String getMaiId() {
		try {
			BufferedReader buf;
			buf = new BufferedReader(new FileReader(maiIdFile));
			String maiId = buf.readLine();
	    	buf.close();
	    	return maiId;
		} catch (FileNotFoundException e) {
            throw new RuntimeException(e);
		} catch (IOException e) {
            throw new RuntimeException(e);
		}
    }
    
    public synchronized PrivateKey getPrivateKey() {
		try {
			BufferedReader buf;
			buf = new BufferedReader(new FileReader(privateFile));
			String keyStr = buf.readLine();
	    	buf.close();
	    	return crypto.readPrivateKey(keyStr);
		} catch (FileNotFoundException e) {
            throw new RuntimeException(e);
		} catch (IOException e) {
            throw new RuntimeException(e);
		} catch (InvalidKeySpecException e) {
            throw new RuntimeException(e);
		}
    }
    
    public synchronized PublicKey getPublicKey() {
		try {
			BufferedReader buf;
			buf = new BufferedReader(new FileReader(publicFile));
			String keyStr = buf.readLine();
	    	buf.close();
	    	return crypto.readPublicKey(keyStr);
		} catch (FileNotFoundException e) {
            throw new RuntimeException(e);
		} catch (IOException e) {
            throw new RuntimeException(e);
		} catch (InvalidKeySpecException e) {
            throw new RuntimeException(e);
		}
    }
    
    public synchronized boolean clearStore() {
    	return clearPrivateKey() && clearPublicKey() && clearMaiId();
    }
    
    private synchronized boolean clearMaiId() {
    	return maiIdFile.delete();
    }
    
    private synchronized boolean clearPrivateKey() {
    	return privateFile.delete();
    }
    
    private synchronized boolean clearPublicKey() {
    	return publicFile.delete();
    }
    
    public synchronized File createArmoredPublicKeyFile() {
    	File keyFile;
		try {
			keyFile = File.createTempFile("publicKey", ".pub", cacheDir);
	        FileWriter keyFileWriter = new FileWriter(keyFile);
	        keyFileWriter.write(getArmoredPublicKeyString());
	        keyFileWriter.close();
	        return keyFile;
		} catch (IOException e) {
            throw new RuntimeException(e);
		}
    }
    
    public synchronized String getArmoredPublicKeyString() {
    	return Crypto.armorPublicKey(getPublicKey());
    }
    
    public synchronized String getPrivateKeyString() {
    	return Crypto.base64Encode(getPrivateKey().getEncoded());
    }
    
    public synchronized SignedDataFiles createSignedDataFiles(String data) {
		try {
			byte[] signature = sign(data.getBytes());
        
	        File dataFile = File.createTempFile("data",	".json", cacheDir);
	        FileWriter dataFileWriter = new FileWriter(dataFile);
	        dataFileWriter.write(data);
	        dataFileWriter.close();
    	
			File sigFile;
			sigFile = File.createTempFile("data", ".json.sig", cacheDir);
	        BufferedOutputStream sigOut = new BufferedOutputStream(new FileOutputStream(sigFile));
	        sigOut.write(signature);
	        sigOut.close();
	        
	        SignedDataFiles sdf = new SignedDataFiles(dataFile, sigFile);
	        return sdf;
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
    }
}